5W - BGP 실습
개요
이번 주차는 저번 주차에 못 본 BGP에 대해 다룬다.
실리움에서 BGP 관련 기능은 클러스터 노드 간 라우팅을 도와주거나, 로드밸런서 서비스의 IP를 외부로 전파하는데 사용된다.
BGP는 라우터에 걸어주는 동적 라우팅 프로토콜이기 때문에 라우팅 관련한 기능들에 활용할 수 있다고 이해하면 편하겠다.
아직 BGP에 대한 이해도가 부족한지 덜거덕거리다 원하는 실습을 다 마치진 못했다.
예정했던 기본 실습 흐름은 다음과 같다.
- BGP 세팅
- 네이티브 라우팅 모드에서 bgp를 통한 클러스터 통신 활성화
- IP 외부 전파 - 얼추 50퍼까지 하고 뒷부분 실패
- 트래픽 경로 최적화
기본 지식 - 라우팅 프로토콜의 종류와 용어
인터넷은 결국 패킷의 경로를 안내하는 라우터들의 연결로 이뤄져 있다.
이곳저곳 패킷이 들쑤시지 않도록, 효율적으로 갈 수 있도록 이 라우터 간의 연결을 관리하는 프로토콜이 라우팅 프로토콜이다.
라우팅 경로를 지정하는 방식에 따라 사람이 직접 설정하는 정적 라우팅, 경로를 알아서 결정하는 동적 라우팅이 있다.
[1]
동적 라우팅에서는 내부에 대한 건지 외부에 대한 건지를 기준으로 크게 두 가지로 나뉜다.
그리고 경로를 결정하는 기준에 따라서 또 두 가지로 또 분류되어 최종적으로 RIP, IGRP, OSPF, BGP 등의 프로토콜로 나뉘는 것이다.
(근데 BGP는 지금까지 이해한 바에 따르면 내부용으로도 사용 가능한 프로토콜이다.)
내외부가 무얼 말하는지 이해하기 위해 용어에 대해서 간략하게 이해해보자.
- Autonomous System
- 자율 시스템, 관리 도메인
- 하나의 네트워크 관리자에 의해 관리되는 라우터 집단.
- 즉 기업 내 구성된 네트워크 전체 구성이라고 생각해도 좋다.
- AS는 네트워크의 내부, 외부를 나누는 기준으로 사용된다.
- 한 AS에는 ASN, 망식별 번호가 또 부여된다.
- AS는 65000개 정도 사용되며, 이중 64512~65534가 사설 AS, 또 65535는 예비용으로 사용된다.
- BGP의 AS 번호는 IANA에서 관리되며 또 국가 별로 AS 번호를 할당하는 KISA가 있다.
- 3대 통신사 KT, SK, LG는 9659, 10059, 9318이라고 한다.
- Hop
- 라우터를 몇 개나 지나가는지를 나타내는 용어
- 라우터를 두 개 걸쳐서 패킷이 전달됐으면 홉이 2인 것이다.
- 컨버전스
- 네트워크 변화, 상태 업데이트를 하는 일련의 동작을 말한다.
간단하게 IGP와 EGP의 차이가 정리된 표다.
BGP
Border Gateway Protocol, 경계 관문 프로토콜은 라우팅 프로토콜의 일종으로, L3 계층에 위치한다.
인터넷에서 데이터를 전송하는데 가장 적합한 네트워크 경로를 결정하는 일련의 규칙이다.[2]
인터넷을 타고다니면서 가급적 효율적인 경로를 선택하는데 도움을 준다는 것이다.
가장 간단하게 요약하자면 라우터들이 자신이 속한 네트워크 경로를 주변 라우터에 전파하고 공유하는 프로토콜이다.
동작 방식
BGP는 라우터들에 대해 설정하는 프로토콜로, 각 라우터는 동료(peer, neighbor) 개념을 사용해 자신과 연결된 라우터를 분류할 수 있다.
가령 A 라우터에서 B 라우터가 연결돼있다 할 때, 설정 상으로도 BGP 동료 설정을 해주어야 한다는 것이다.
C 라우터는 A 라우터에 바로 연결돼있지 않더라도 B라우터와 연결돼 있다면 A에서 C에 대해 동료 설정을 하는 것도 가능하다.
즉 홉을 건너뛰는 라우터 간에도 연결 설정이 가능하다는 것이다.
(연결 설정 자체는 수작업으로 해야 한다.)
BGP는 근본적으로 이 동료 간의 통신에 대한 설정을 하는 프로토콜이라 보면 된다.
이때 TCP 179 포트로 연결을 수립하며, 동료로 맺어지면 60초마다 keepalive 메시지로 상태를 확인한다.
오른쪽의 라우터가 자신의 AS의 경로를 광고하는 과정이다.[3]
AS1에는 경계에 해당하는 두 개의 라우터가 있고, 각각 AS2, AS3에 연결돼있다.
AS2가 경로를 광고하고, 이를 통해 AS1의 AS3로 향하는 라우터도 이 경로를 알게 된다.
그래서 이걸 또 AS3로 전파한다.
이때 점차 앞에 AS번호가 붙는 게 보이는데, 이를 통해 다른 곳에서 저 IP로 어떤 전달을 하고 싶다면 해당 AS들을 걸쳐야 한다는 것을 알 수 있다.
유형
라우팅 되는 위치에 따라 두 가지로 나뉘는데, 실질적인 차이는 bgp 경로가 다른 피어로 전파되는 방식이다.
- 외부 - eBGP
- 외부 피어에서 학습한 경로는 모든 피어에 다시 알려진다.
- 내부 - iBGP
- 내부 피어에서 학습한 경로는 모든 외부 피어에만 다시 알려진다.
- 이건 아래 규칙에서 보면 왜 이리 되는지 알 수 있다.
규칙
세 가지 조건이 충족돼야 bgp가 정상적으로 동작한다.
- BGP Split Horizon(ibgp 속 3개의 라우터 간)
- ibgp에서 라우터 간 루핑을 방지하기 위한 조건
- 같은 정보를 지네끼리 계속 주고받지 못하게 하고자 하는 것이다.
- ibgp로 광고받은 네트워크는 ibgp로 광고하지 못한다.
- 같은 as 안에 라우터가 3개 있다고 쳤을 때, a가 b에게 c의 정보를 줬다면, b는 이렇게 받은 정보를 광고하지 않는다.
- link state는 목적지까지의 모든 경로를 알고 있어서 이게 필요 없지만, distance vector에서는 홉만 따지기에 이게 필요하다.
- 이를 위해 세가지 방식(full mesh, route reflector, confederation)이 있다고 한다.
- ibgp에서 라우터 간 루핑을 방지하기 위한 조건
- BGP Next Hop(2 as 간)
- next hop은 목적지로 가기 위한 다음 라우터를 말하는데, 일반적으로는 연결된 라우터를 말한다.
- 근데 이웃으로 설정되면 이걸 next hop으로 변경할 수도 있다.
- BGP 동기화 법칙
- 다른 라우터로부터 전송된 네트워크 정보를 자신의 라우터에 등록하는 것
ECMP
Equal Cost Multi-Path는 라우팅 경로를 선택할 때 사용되는 하나의 전략이다.[4]
직역하면 그대로, 동일한 비용이 드는 여러 경로가 있다면 모든 경로를 선택하는 기능.
(이건 프로토콜이 아니다.)
분산하는 역할을 수행하나 같은 비용이라고 해서 실제 같은 트래픽 전송 속도를 보장하는 것은 아니다.
또한 세션 연결이 중요한 트래픽을 분산시키는 경우도 있기 때문에 사용에 주의가 요구된다.
ECMP는 다양한 영역에서 사용되며 다양한 프로토콜이 지원한다.
- OSPF: SPF 계산 결과 metric이 같은 두 경로 → ECMP로 둘 다 라우팅 테이블에 등록
- BGP: 같은 prefix에 대해 속성이 동일한 여러 경로 →
maximum-paths
값만큼 등록 - Linux 커널: 라우팅 테이블에 여러 next-hop 등록 가능 → 커널이 해시 기반으로 트래픽 분산
- 이후에 보겠지만, 커널에서는 해싱할 때 기본적으로 목적지 IP만을 기반으로 한다.
실리움 BGP
실리움에서는 BGP 컨트롤 플레인을 설정할 수 있다.[5]
즉 BGP 통신을 하는 라우터 설정을 자체적으로 할 수 있다.
이 BGP 설정을 함께 하는 클러스터 외부 라우터는 클러스터 내부의 각종 IP 주소로 트래픽을 라우팅할 수 있게 된다.
다시 말해, 클러스터의 파드 IP, 서비스 IP 만으로 외부에서 클러스터에 접근이 가능하게 된다는 말이다.
말 그대로 라우팅 설정인 만큼, 비단 외부 라우터에 IP를 공유하는 목적으로만 사용되는 것은 아니다.
네이티브 라우팅 모드를 사용할 때 클러스터 내 다른 대역 간 통신이 가능하게 만드는 데에도 사용될 수 있다.
실리움 BGP 기능은 꽤 많은 발전을 거듭하고 있는데, 문서를 보면 bgp control plane과 bgp peering policy(legacy)라 표시된다.
이전에는 kube-router, bird와 같은 라우터 역할을 수행할 수 있는 외부 소프트웨어를 기반으로 BGP 설정을 하는 방식을 지원했다.
이후 실리움에서는 GoBGP라는 툴을 기반으로 bgp 설정을 완전히 실리움 시스템으로 통합시켰으며, 일종의 2버전으로서 bgp control-plane이 생기게 됐다.
현재 지원 중인 기능인 BGP 컨트롤 플레인을 사용해본다.
--set bgpControlPlane.enabled=true
관련 리소스
아래 그림이 BGP 컨트롤 플레인 기반의 리소스들로 실리움이 동작하는 전체 구조를 담고 있다.[6]
(이전에는 BGPPeeringPolicy라는 리소스 하나 뿐이었다.)
파란색이 커스텀 리소스로 직접 사용자가 설정해주면 된다.
- CiliumBGPClusterConfig - 어떤 노드에 BGP 설정할 건지 지정
- CiliumBGPPeerConfig - BGP 피어 세부 설정
- CiliumBGPAdvertisement - 어떤 걸 광고할지 BGP 라우팅 테이블 설정
- CiliumBGPNodeConfiguration - 노드 별로 세밀하게 제어할 때 설정하며, 필수적이진 않음
결과적으로 우측 하단처럼 파드 cidr, 서비스, 실리움의 ip풀 등의 주소를 bgp 피어들이 받아 라우팅할 수 있게 된다.
각 리소스 양식을 조금 더 상세히 파보자.
CiliumBGPClusterConfig
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPClusterConfig
metadata:
name: cilium-bgp
spec:
nodeSelector:
matchLabels:
rack: rack0
bgpInstances:
- name: "instance-65000"
localASN: 65000
peers:
- name: "peer-65000-tor1"
peerASN: 65000
peerAddress: fd00:10:0:0::1
peerConfigRef:
name: "cilium-peer"
- name: "peer-65000-tor2"
peerASN: 65000
peerAddress: fd00:11:0:0::1
peerConfigRef:
name: "cilium-peer"
클러스터에서 BGP 라우터 역할을 할 노드를 선정하고, 외부 라우터에 대한 설정을 하는 리소스이다.
노드 셀렉터를 통해 노드를 선택하고 여기에 대해 bgp 세팅을 하는 것을 확인할 수 있다.
동료에 대한 세팅을 하는 것이 보이는데, peerConfigRef
는 아래 커스텀 리소스를 뜻한다.
CiliumBGPPeerConfig
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeerConfig
metadata:
name: cilium-peer
spec:
timers:
holdTimeSeconds: 9
keepAliveTimeSeconds: 3
authSecretRef: bgp-auth-secret
ebgpMultihop: 4
gracefulRestart:
enabled: true
restartTimeSeconds: 15
families:
- afi: ipv4
safi: unicast
advertisements:
matchLabels:
advertise: "bgp"
BGP 라우터 세팅을 할 때 들어가는 여러 설정들은 이 리소스에서 진행한다.
이 설정이 기반이 되어 노드에 BGP 라우터 설정이 들어간다고 보면 되겠다.
구체적으로 다음의 기능들을 설정할 수 있다.
- MD5 암호
- 피어 간 보안을 위해 인증을 요구하는 기능으로, 동일한 비밀키를 세팅하면 이를 MD5 해싱에 활용한다.
authSecretRef: bgp-auth-secret
와 같은 식으로 시크릿을 지정하며, 해당 시크릿에는password
키를 넣어주면 된다.
- 타이머 - bgp 연결 간 시간 변수
connectRetryTimeSeconds: 120
- 최소 5holdTimeSeconds: 90
- 최소는 9keepAliveTimeSeconds: 30
- 최소 3
- EBGP Multihop
- 기본으로 eBGP에서 뛰는 최대 홉은 1이다.
ebgpMultihop
으로 이 제한을 올릴 수 있다.
- 우아한 재시작
- bgp 세션에 오픈 메시지로 재시작 가능함을 피어에 알린다.
- 이를 통해 순간 노드가 죽어도 바로 라우팅 테이블을 수정하지 않고 재시작을 기대하며 트래픽 전달을 중단하지 않는다.
gracefulRestart
필드를 통해 설정한다.
또 동료에게 어떤 것을 광고할지에 대한 설정을 families.advertisements
필드에서 할 수 있다.
이 부분은 또 바로 아래 커스텀 리소스를 매칭하는 방식으로 연결된다.
CiliumBGPAdvertisement
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "PodCIDR"
attributes:
communities:
standard: [ "65000:99" ]
# 가까운 경로 가중치
localPreference: 99
여기에서 동료에게 무엇을 광고할지 지정한다.
advertisementType
에는 PodCIDR, CiliumIPAMPool, Service를 넣을 수 있다.
서비스를 설정할 경우, 아래와 같이 어떤 타입의 서비스를 광고할 건지도 세부 지정 가능하다.
- advertisementType: "Service"
service:
addresses: # <-- specify the service types to advertise
- LoadBalancerIP
위 설정은 로드밸런서 IP를 광고하지만, Cluster IP를 넣어 클러스터의 서비스 IP 대역을 광고하는 것도 가능하다.
실습 환경 구성
이번 환경은 이전 실습 환경과 크게 다르지 않다.
클러스터를 두 대역에 구성하며 중간 라우터 역할을 하는 VM을 배치한다.
근데 라우터 역할을 하는 VM에 아예 FRR[7]이라는 리눅스로 돌리는 오픈소스 라우터를 사용한다.
BGP는 라우터 간 프로토콜인 만큼 라우터 설정을 적용할 수 있는 그럴싸한 라우터가 필요하기 때문이다.
참고로 FRR은 여러 다양한 RFC 규정 프로토콜들을 지원하고 있다.
FRR은 각 기능을 수행하는 라우터 기능을 별도의 데몬 프로세스로 띄워서 실행하며, 중앙 관리자 프로세스로 zebra를 둔다.
FRR에는 다음과 같이 세팅을 해준다.
sed -i "s/^bgpd=no/bgpd=yes/g" /etc/frr/daemons
NODEIP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
cat << EOF >> /etc/frr/frr.conf
!
router bgp 65000
bgp router-id $NODEIP
bgp graceful-restart
no bgp ebgp-requires-policy
bgp bestpath as-path multipath-relax
maximum-paths 4
network 10.10.1.0/24
EOF
BGP 데몬을 활성화 시키고, 이후 설정 정보를 기입했다.
간단하게만 설명하자면,
- AS 65000으로 BGP 설정
- 라우터의 id는 자신 ip로 했는데, 보통 안 겹치게 하기 위해 이렇게 하는 편이다.
- 안전한 재시작 설정
- eBGP에서 별도의 정책 설정 없이 광고 가능
- ECMP
- AS_PATH 차이를 무시하고 비용만 같다면 여러 경로 활성화
- 멀티 패스 최대 4개
- 광고할 ip 대역은 10.10.1.0/24
추가 사항으로, BGP 컨트롤 플레인 설정을 헬름에 넣어 세팅을 진행한다.
--set routingMode=native --set autoDirectNodeRoutes=true --set bgpControlPlane.enabled=true \
또한 네이티브 라우팅을 하면서 기존에는 같은 대역 노드 간 라우팅 테이블이 알아서 추가되도록 autoDirectNodeRoutes을 설정했으나 이것도 비활성화했다.
BGP를 세팅하게 되면 BGP로 라우팅 경로가 잡히기 때문에 없어도 상관이 없다.
첫 설정을 하면 BGP 설정은 켜져는 있다.
cilium config view | grep bgp
참고로 bgp는 동적으로 라우팅 정보를 갱신하다보니 상태 정보를 일일히 커스텀 리소스에 업데이트하면 api 서버 부하가 심해진다.
그래서 --set bgpControlPlane.statusReport.enabled=false
로 아래 설정은 끄고 운영하는 케이스도 많다.
네트워크 설정으로 보면 다른 대역으로 향하는 라우팅 테이블이 없는 것을 확인할 수 있다.
for i in ctr w1 w0 ; do echo ">> node : k8s-$i <<"; vagrant ssh k8s-$i -c 'ip -c route' ; echo; done
이 상태에서는 이전 실습과 동일하게 다른 대역의 노드 간 통신이 불가능하다.
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
BGP 실습
본격적으로 BGP 세팅을 해보자.
라우터 세팅
먼저 라우터에 들어가 확인해보자.
ps -ef |grep frr
ss -tnlp | grep -iE 'zebra|bgpd'
FRR은 여러 하위 프로세스를 가동시키는데, zebra는 메인 라우팅 관리자 역할을 하는 프로세스이다.
zebra는 라우팅 프로토콜 데몬들에 대한 관리, 커널과의 통신 역할을 수행한다.
이외에 bgp 데몬, 스태틱 데몬이 돌아가는 게 보인다.
먼저 내부에서는 각 데몬이 활성화 된 채로 돌아가고 있으며, BGP데몬은 BGP 포트인 TCP 179를 외부로 오픈하고 있다.
FRR은 마치 하드웨어 네트워크 장비에 콘솔 연결해서 조작하듯이 vtysh이란 콘솔을 따로 열어 조작을 할 수 있다.
vtysh -c 'show running'
처음 라우터에 넣어준 설정이 그대로 보인다.
vtysh을 이용해 모든 설정을 하는 것도 가능하지만, 실습에서는 펀쿨섹하게 설정 파일을 이용할 것이다.
설정 상태를 보는 명령어들을 미리 알아두자.
vtysh -c 'show ip bgp summary'
#이건 그냥 내거 광고중인 것.
vtysh -c 'show ip bgp'
vtysh -c 'show ip route'
journalctl -u frr -f
*>는 유효한 최적 경로로서 실제 라우팅 테이블에 올린 경로를 뜻한다.
자신이 광고하는 경로에 대해서는 다음 홉이 0으로 찍히며 자기 자신이 출발지라는 걸 뜻한다.
다른 곳에서 들어온 경로라면 해당 지점을 가리킬 것이다.
이제 실리움과 이웃을 맺기 위한 설정을 넣는다.
cat << EOF >> /etc/frr/frr.conf
neighbor CILIUM peer-group
neighbor CILIUM remote-as external
neighbor 192.168.10.100 peer-group CILIUM
neighbor 192.168.10.101 peer-group CILIUM
neighbor 192.168.20.100 peer-group CILIUM
EOF
systemctl daemon-reexec && systemctl restart frr
systemctl status frr --no-pager --full
먼저 이웃으로 실리움 그룹을 만들고, 여기에 BGP 라우터 역할을 할 노드의 IP를 추가했다.
또한 이웃을 외부로 설정했는데, 이렇게 설정하면 자신의 내부가 아니기에 별도의 추가 설정을 요구하지 않는다.
설정이 적용되면 이전에는 제대로 보이지 않았던 요약 정보가 출력된다.
물론 아직 노드에 설정을 한 게 없기에 실제로는 아무것도 오고가는 것이 없다.
참고로 굳이 네트워크 장비 다루듯 설정하고 싶다면 아래처럼 하는 것도 가능하다..
vtysh
---------------------------
?
show ?
show running
show ip route
# config 모드 진입
conf
?
## bgp 65000 설정 진입
router bgp 65000
?
neighbor CILIUM peer-group
neighbor CILIUM remote-as external
neighbor 192.168.10.100 peer-group CILIUM
neighbor 192.168.10.101 peer-group CILIUM
neighbor 192.168.20.100 peer-group CILIUM
end
# 설정 저장
write memory
exit
---------------------------
클러스터 세팅
이제 위에서 본 각 커스텀 리소스로 세팅을 진행해보자.
추이를 지켜보고자 미리 몇 가지 명령을 실행시켜둔다.
# 신규 터미널 1 (router) : 모니터링 걸어두기!
journalctl -u frr -f
# 신규 터미널 2 (k8s-ctr) : 반복 호출
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
bgp control plane을 활성화시키면 추가적인 커스텀 리소스가 생기는 것을 볼 수 있다.
실리움 1.18로 올라오면서 대부분의 리소스 버전이 메이저로 올랐으므로, 이전 버전의 실리움을 사용한다면 해당 리소스들의 버전 체크를 해볼 필요가 있다.
BGP를 사용할 노드를 라벨 셀렉팅하기 위해 먼저 라벨을 붙여준다.
kubectl label nodes k8s-ctr k8s-w0 k8s-w1 enable-bgp=true
그리고 BGP와 관련된 커스텀 리소스를 생성한다.
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "PodCIDR"
---
apiVersion: cilium.io/v2
kind: CiliumBGPPeerConfig
metadata:
name: cilium-peer
spec:
timers:
holdTimeSeconds: 9
keepAliveTimeSeconds: 3
ebgpMultihop: 2
gracefulRestart:
enabled: true
restartTimeSeconds: 15
families:
- afi: ipv4
safi: unicast
advertisements: # 위에 커스텀 리소스
matchLabels:
advertise: "bgp"
---
apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
name: cilium-bgp
spec:
nodeSelector:
matchLabels:
"enable-bgp": "true"
bgpInstances:
- name: "instance-65001"
localASN: 65001
peers:
- name: "tor-switch"
peerASN: 65000
peerAddress: 192.168.10.200 # router ip address
peerConfigRef: # 위에 커스텀 리소스
name: "cilium-peer"
ClusterConfig에서 위에서 미리 설정한 노드 라벨을 고른다.
라우터와 다른 AS를 설정한 것도 확인할 수 있다.
설정을 적용하자마자 라우터 시스템 로그에 연결 표시가 뜬다면 성공이다.
막상 실리움 노드에서 보면 BGP 노드로서 179 포트를 열고 있지는 않다.
ss -tnlp | grep 179
# cilium bgp 정보 확인
cilium bgp peers
cilium bgp routes available ipv4 unicast
그럼에도 광고를 주고 받는 기능은 수행할 수 있다.
자신이 뭘 광고하고 있는지도 표시된다.
179 포트가 열려있지 않은 부분은 확실하진 않은데, 커널 단에서 트래픽의 경로를 수정해서 처리하기라도 하는 걸까?
커스텀 리소스로 정보를 확인할 수도 있다.
kubectl get ciliumbgpadvertisements,ciliumbgppeerconfigs,ciliumbgpclusterconfigs
kubectl get ciliumbgpnodeconfigs -o yaml | yq
보다시피 연결된 상대에 대한 정보 정보가 들어가는 것이 보인다.
라우터쪽에서 보면 이제 재밌는 정보가 보인다.
먼저 라우팅 테이블을 조회하면 bgp로 전파된 클러스터 ip 대역이 테이블에 추가됐다.
아울러 bgp 이웃에 대한 정보도 최신화된 것이 보인다.
클러스터 내부 통신
이 상태에서 네이티브 라우팅 모드인 클러스터는 통신이 가능한가?
# k8s-ctr tcpdump 해두기
tcpdump -i eth1 tcp port 179 -w /tmp/bgp.pcap
# router : frr 재시작
systemctl restart frr && journalctl -u frr -f
# bgp.type == 2
termshark -r /tmp/bgp.pcap
# 분명 Router 장비를 통해 BGP UPDATE로 받음을 확인.
cilium bgp routes
ip -c route
언뜻 보면 방금 파드 cidr 대역을 광고를 시켰고 이것이 라우터에 적용이 됐기에 통신이 가능해져야 할 것 같다.
그러나 막상 각 노드를 보면 bgp로 추가된 라우팅 테이블이 전혀 보이지 않는다.
패킷을 뜯어보면 bgp 통신은 정상적으로 이뤄지는 것도 보이는데도 말이다.
실제로도 다른 대역 간 노드 통신은 여전히 실패한다.
이 문제의 이유는 아직 내게는 확실하지 않다.
가시다님의 의견을 기반으로 생각하자면,
BGP 컨트롤 플레인의 근간은 Go BGP로, 여기에서는 자신 노드에 대한 테이블(FIB, Forwading Information Base) 업데이트는 frr에서 보았던 zebra를 사용한다.[8]
그런데 실리움에서는 기본적으로 zebra를 활용하지 않는 것으로 보인다.
대체로 클러스터를 구축하는 다른 환경에서는 이러한 문제가 발생하지 않을 가능성이 높다.
다른 대역의 노드를 합쳐 한 클러스터로 묶는다면 각 노드의 디폴트 게이트웨이를 두 대역을 잇는 라우터로 설정할 것이기 때문이다.
그러면 해당 라우터에 BGP 설정을 건다면 결국 라우터가 트래픽을 라우팅해줄 것이다.
다른 방향으로 생각해본 것은 다른 AS 간의 통신으로 라우터의 광고 대역이 전달 안 된 것은 아닐까 하는 것이다.
실리움 에이전트에서는 import하는 대역이 없는 것으로 보인다.
이때 allow-local이란 정책에 아무것도 없는 게 보이는데, 이게 혹시 내부 AS의 광고를 받기 위한 정책이 아닌가 하는 추측이다.
bgp에 대해 조금 더 공부해보면 답이 나올 것 같기도..
아무튼 간단하게 해결할 수는 있다.
# k8s 파드 사용 대역 통신 전체는 eth1을 통해서 라우팅 설정
ip route add 172.20.0.0/16 via 192.168.10.200
ip route add 172.20.0.0/16 via 192.168.20.200
외부에서 접근해보기
저번 주에 말했듯, 실리움은 메탈LB를 대체할 수 있다.
이번에는 본격적으로 서비스 로드밸런서 용으로 BGP를 활용해본다.
먼저 로드밸런서용 ip 풀을 만들고, bgp 광고 설정을 한다.
apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
name: "cilium-pool"
spec:
allowFirstLastIPs: "No"
blocks:
- cidr: "172.16.1.0/24"
---
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements-lb-exip-webpod
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "Service"
service:
addresses:
- LoadBalancerIP
selector:
matchExpressions:
- { key: app, operator: In, values: [ webpod ] }
대상을 라벨로 선택하고, 해당 대상의 어떤 것을 광고할지 지정했다.
LoadBalancerIP가 흔히 아는 외부 IP이다.
다음 로드밸런서를 하나 만든다.
kubectl patch svc webpod -p '{"spec": {"type": "LoadBalancer"}}'
라우터에서 bgp 정보를 보면, 성공적으로 로드밸런서 ip가 광고된 것을 확인할 수 있다.
통신도 문제없이 가능하다.
로드밸런서가 모든 노드로 트래픽을 보내는 것 자체는 기본적인 동작에 속한다.
보다시피 가중치 1로 전부 동일한 비용을 가지기 때문에, 라우터 vm에서 해당 ip로 트래픽을 보내면 공평하게 트래픽이 분배될 것이다.
sudo vtysh -c 'show ip bgp 172.16.1.1/32'
현재 실습에서는 다른 노드 간 통신이 가능하도록 세팅하지 않았다.
LBIP=172.16.1.1
curl -s $LBIP
# 반복 접속
for i in {1..100}; do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
while true; do curl -s $LBIP | egrep 'Hostname|RemoteAddr' ; sleep 0.1; done
그래서 다른 대역으로 트래픽이 빠지면 이를 파드가 위치한 곳으로 트래픽을 라우팅할 방법이 없어 에러가 난다.
아무튼 전체 노드로 트래픽이 제대로 분산되고 있다는 것.
현재는 모든 노드에 걸쳐 파드가 배치돼있는데, 그렇지 않은 케이스에는 어떻게 될까?
kubectl scale deployment webpod --replicas 2
사실 다른 대역인 w0의 파드가 사라지길 바랐는데, 이걸로도 트래픽 정책에 대한 실습은 할 수 있으니 상관없겠다.
이번에도 성공 트래픽에 공백이 발생한다.
파드의 위치와 상관없이 대상이 된 노드들이 IP를 광고하고 있는 것이다.
물론 이렇게 클러스터 내 대역이 다른 특수한 상황이 아니었다면 이것은 그다지 문제가 되지 않았을 것이다.
다만 파드가 없는 노드가 트래픽을 받으면 이를 또 라우팅해야 하니 불필요한 트래픽 홉이 생기는 꼴이긴 하다.
아무튼 이걸 해결하는 좋은 방법 중 하나는 트래픽 정책을 수정하는 것이다.
kubectl patch service webpod -p '{"spec":{"externalTrafficPolicy":"Local"}}'
트래픽 정책 필드는 엄밀하게 노드로 들어온 트래픽이 다른 노드의 파드로 가지 않고 오직 자신에 위치한 파드로만 가게 하는 설정이다.
실리움에서는 이 기능에 대해 아예 로드밸런서로 하여금 실제로 백엔드가 위치하는 노드로만 트래픽이 가도록 만들어준다.
보다시피 한 노드는 파드가 없기에 아예 해당 IP를 광고하지 않는다.
또한 이제부터는 트래픽을 받은 노드는 오직 자신의 파드에만 트래픽을 전달할 것이다.
테스트해보자.
# 신규터미널 (3개) : k8s-w1, k8s-w2, k8s-w0
tcpdump -i eth1 -A -s 0 -nn 'tcp port 80'
# k8s-ctr 를 경유하거나 등 확인 : ExternalTrafficPolicy 설정 확인
LBIP=172.16.1.1
for i in {1..100}; do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
일단 트래픽은 원활하게 오고가는 것이 보인다.
다만 아예 한쪽으로 트래픽이 몰리는 상황은 기대하지 않았다!
보다시피 받은 노드는 다른 곳으로 트래픽을 전혀 보내지 않는다.
또한 받은 트래픽의 소스 ip를 보존하는 것 역시 확인할 수 있다.
그러나 하나의 문제가 더 생겼는데, 분명 ECMP 설정을 했음에도 트래픽이 분산되지 않는 문제를 해결해야 한다.
이건 커널 단의 ECMP 해시 정책 문제로, 라우팅 테이블 다중 경로가 적혀있어 커널에서는 트래픽을 동등하게 분산하기 위해 해시를 사용한다.
근데 통산적으로 커널 단의 해시는 L3 레벨, 즉 목적지 IP 값만 기반으로 해시를 하고 보낼 위치를 정하기에 같은 주소로 향하는 요청은 항상 같은 곳으로 가게 되는 불상사가 발생한다.[9]
이를 해결하려면 커널 해시 정책을 수정해야 한다.
L4 레벨로 해시 정책을 수정하면 출발 포트 정보도 같이 이용해 해싱을 하게 되는데, 출발 포트는 당연히 ephemeral이므로 제대로 해시 처리 될 것을 기대할 수 있다.
sudo sysctl -w net.ipv4.fib_multipath_hash_policy=1
echo "net.ipv4.fib_multipath_hash_policy=1" >> /etc/sysctl.conf
for i in {1..100}; do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
문제 발생 - 경로 추적 실패
테스트를 하는데 문제가 발생했다.
내가 생각하지 않은 방향으로 결과가 나왔다.
몇 가지 트래픽이 여전히 실패하고 있는 것으로 보인다.
먼저 현재 파드는 k8s-ctr, k8s-w0에 위치하며 ctr과 같은 대역에 위치한 w1에는 파드가 없는 상태이다.
w1 노드에는 파드가 없고, 현재 테이블 상에서도 경로가 명시되지 않으니 절대 트래픽을 받는 경우는 없어야만 한다.
근데 왜인지 계속 한번씩 트래픽이 가는 것이 포착된다.
라우팅 경로 상에서는 다른 곳으로 갈 여지가 없어 보이는데 말이다.
노드별 패킷을 캡쳐하니 여전히 포워딩이 되는 트래픽이 있었다.
혹시 ctr 노드가 트래픽을 받은 후 포워딩을 하는 것인가 했는데, 시간 상으로는 오히려 트래픽을 받아서는 안 되는 w1이 트래픽을 받고 ctr로 넘기고 있다.
모든 트래픽을 w1이 받은 후에, ctr로 넘긴다.
재밌는 건 간혹 실패하는 트래픽 역시 흐름 자체는 같다는 것이다.
목적지 ip는 172.16.1.1이고 라우팅 테이블에는 ctr, w0이 명시된 상태.
그렇다면 정상적이라면 해당 노드로 트래픽이 곧장 가야할 것 같다.
혹시나 하는 마음에 찍어둔 설정.
내부 트래픽 정책도 로컬로 바꾸자 문제가 해결되는 듯 보였다.
어째서..?
말이 안 된다고 생각해서 다시 날려보니 이번에는 다시 처음의 상태가 나왔다.
ping 요청도 안 들어가는 게 보였는데, 이건 생각해보니 당연한 것 같았다.
노드의 에이전트는 icmp 프로토콜까지 받도록 설정이 안 돼 있으니..
..?
아무것도 건드린 것 없이 그냥 패킷따면서 계속 시간을 끌고 있는데, 이번에는 또 다 성공한다.
그런데 여전히 w1로 트래픽이 가는 것은 여전하니, 문제는 해결되지 않았다고 볼 수 있다.
방금 전까지는 경과된 시간이 영향을 주는 것 같아 캐시된 정보가 있는 게 아닐까 싶었다.
그런데 또 w1쪽 패킷을 따고 있던 차에 다시 실험하니 요 모양 요 꼴이다.
ARP로 누가 기본 클러스터 대역에 해당 IP를 누가 받는지 보려고 했는데, 받아내는 호스트가 없다.
테이블에서 via가 걸려있으니 해당 IP에 대해 직접 ARP를 날리진 않을 것이다.
해보니 MAC 주소는 분명 제대로 매핑돼있다.
그럼 역시 로드밸런서 ip에 대해 eth0에서 패킷을 받아내는 건 ctr 노드가 돼야 한다.
현재까지 봤을 때 어떨 때는 분명하게 w1로 트래픽이 가지 않고, 이때는 모든 통신이 성공한다.
그러다 어느 순간에는 갑자기 w1이 트래픽을 받아내서는 통신이 실패하는 상황이 발생한다.
문제가 정확하게 뭔지는 모르겠으나, bgp가 경로를 갱신하는 타이밍이 있고 그때 문제가 발생하는 걸까?
ecmp 관련하여 내가 생각치 못한 경로 결정이 이뤄지고 있는 게 아닐까 싶다.
그렇다면 당장 이 문제를 해결하는 것이 조금 어려워보인다.
bgp의 각종 설정들을 뜯어보면서 이런 상황이 발생할 수 있는 케이스를 구체화시켜야 할 것 같다.
ecmp 해싱 문제일까 싶어, 모든 노드에 대해서 커널 ecmp 설정 값을 맞춰본 후에는 일단 문제가 발생하지 않고 있다.
그러나 아까도 문제가 해결된 것 같더니만 또 문제가 생기던 순간이 있어 당장 이것이 답이었다고 확답하긴 어려울 것 같다.
당장 내가 이해한 바로는 커널의 ecmp는 트래픽을 라우팅할 때 자신이 보낼 경로를 분산하기 위해 존재한다.
그러니 내가 curl 요청을 날리는 라우터 vm에서만 설정을 해도 별 이상이 없어야 한다.
결론
이 문제를 정확하게 해결하지 못하면 다음 실습에도 차질이 있겠다 싶어서 아쉽지만 일단 여기에서 1차적으로 끝마쳐야 할 것 같다.
트래픽 인입 경로 설정과 관련해 최적화 기법에 대한 비교글만 정리하고 이번 노트는 마친다.
여태 bgp로 설정을 해왔지만 여기에 리소스 설정, 실리움 설정에 따라 트래픽의 경로를 다양하게 변형할 수 있다.
- 서비스의 트래픽 정책
- 클러스터 - 모든 설정된 bgp 노드로 트래픽 경로가 설정된다.
- 최악의 경우 실제 워크로드가 존재하지 않는 노드로 트래픽이 간다.
- 그러면 해당 노드에서 포워딩으로 워크로드가 있는 노드로 트래픽을 보내게 되어 불필요한 홉이 추가된다.
- 이때 DSR(Direct Server Return) 기능을 사용하면 응답 트래픽은 추가된 홉을 거치지 않아 그나마 성능이 최적화된다.
- DSR 기능을 활성화하면 실리움은 해당 트래픽에 대해서는 포워딩 시 GENEVE를 사용하여 추가 헤더에 소스 IP를 부착하여 전달한다.
- 이 정보를 통해 DSR을 해내는데, 이것은 실리움이 커널 영역에서 수행하는 작업이다.
- 로컬 - 워크로드가 있는 노드로만 트래픽 경로가 설정된다.
- 이때는 노드 간 포워딩이 일어나지 않는 것이 기본 설정이다.
- 그래서 이때는 라우터에서 랜덤하게 트래픽을 보내주기만 하면 된다.
- 다만 최소한 BGP에서 ECMP 기능이 활성화될 필요가 있다.
- 로드밸런싱이 전적으로 ECMP로 수행되기 때문이다.
- 클러스터 - 모든 설정된 bgp 노드로 트래픽 경로가 설정된다.
위 실습에서 보고자 했던 것이 서비스 트래픽 정책에 따른 변화였고 이후 어떤 방식들이 있는지 구체적으로 보고자 했는데, 초반에서 어그러졌다..
이전 글, 다음 글
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - 실리움 기본 소개 | 1 | published | 2025-07-19 |
1W - 클러스터 세팅 및 cni 마이그레이션 | 2 | published | 2025-07-19 |
1W - 기본 실리움 탐색 및 통신 확인 | 3 | published | 2025-07-19 |
2W - 허블 기반 모니터링 | 4 | published | 2025-07-26 |
2W - 프로메테우스와 그라파나를 활용한 모니터링 | 5 | published | 2025-07-26 |
3W - 실리움 기본 - IPAM | 6 | published | 2025-08-02 |
3W - 실리움 기본 - Routing, Masq, IP Frag | 7 | published | 2025-08-02 |
4W - 실리움 라우팅 모드 실습 - native, vxlan, geneve | 8 | published | 2025-08-09 |
4W - 실리움 로드밸런서 기능 - 서비스 IP, L2 | 9 | published | 2025-08-09 |
5W - BGP 실습 | 10 | published | 2025-08-16 |
5W - 클러스터 메시 | 11 | published | 2025-08-16 |
관련 문서
지식 문서, EXPLAIN
이름0 | is-folder | 생성 일자 |
---|
기타 문서
Z0-연관 knowledge, Z1-트러블슈팅 Z2-디자인,설계, Z3-임시, Z5-프로젝트,아카이브, Z8,9-미분류,미완이름0 | 코드 | 타입 | 생성 일자 |
---|